import java.awt.Graphics;
import java.awt.image.ImageObserver;
import java.util.ArrayList;


public class Wave {

	static int direction;
	final int VSEP = 32;
	final int HSEP = 32;
	final int BEGX = 20;
	final int BEGY = 116;
	final int MIN_WAVE_START_HEIGHT = BEGY + VSEP * 4;
	final static int MAX_WAVE_SIZE = 55;
	private static final int MOVE_COOLDOWN_MAXIMUM = 14*InvaderSaver.GAMESPEED_MODIFIER;
	private static final int MOVE_COOLDOWN_MINIMUM = (int)(MOVE_COOLDOWN_MAXIMUM /14.0);
	private static int moveCooldown = MOVE_COOLDOWN_MAXIMUM;
	private static int moveWait;
	private static int InvaderMoveSpeed = 16;
	static int waveNum;
	/**
	 * atScreenEdge is modified by an Invader when it reaches the edge of the screen
	 * Only an Invader can set it true, and Wave sets it false on every update
	 */
	static boolean atScreenEdge;
	
	private ArrayList<Invader> invaders;
	private Bonus bonus;
	
	Wave(){
		direction = 0;
		waveNum = 0;
		invaders = new ArrayList<Invader>();
		bonus = null; // no bonus to start with
		moveWait = moveCooldown;
		atScreenEdge = false;
	}
	
	/**
	 * updates each of the still-living Invaders
	 */
	public void update(){
		
		// first, clean out any dead invaders
		ArrayList<Invader> invaderCopies = new ArrayList<Invader>();
		invaderCopies.addAll(invaders);
		invaders.clear();
		for(Invader i:invaderCopies){
			if(!i.garbageCollectMe()){
				invaders.add(i);
			}
		}
		
		if(moveWait == 1){ // then move the Invaders left/right/down
			// update the moveCooldown to go faster when there are fewer Invaders remaining
			moveCooldown  = MOVE_COOLDOWN_MAXIMUM - (int)((1 - (double)invaders.size()/MAX_WAVE_SIZE) * (MOVE_COOLDOWN_MAXIMUM - MOVE_COOLDOWN_MINIMUM));
			moveWait = moveCooldown;
			boolean advance = false;
			//check to see if any of the Invaders is at the edge of the screen. if so, advance = true and change direction
			if(atScreenEdge)
			{
				advance = true;
				direction *= -1;
				atScreenEdge = false;
			}
			
			for(Invader invader:invaders){
				invader.update(advance, direction*InvaderMoveSpeed, invaders.size());
			}
		}
		else{ // update without moving (this allows asynchronous shooting/etc
			for(Invader invader:invaders){
				invader.update(false, 0, invaders.size());
			}
			moveWait--;
		}
		
		if(bonus!=null){
			bonus.update();
			if(bonus.resetMe()){
				bonus = null;
			}
		}
		
		// with some probability, create a Bonus (assuming there isn't already one
		if(bonus == null && Math.random() < .01){
			int height = 84;
			int speed = 4;
			if(Math.random() < .5){ // .5 chance bonus goes left to right
				bonus = new Bonus(0-SpriteLib.BONUS.image_width, height, SpriteLib.BONUS, speed/InvaderSaver.GAMESPEED_MODIFIER);
			}
			else{ // bonus goes right to left
				bonus = new Bonus(InvaderSaver.getGameWidth()+SpriteLib.BONUS.image_width, height, SpriteLib.BONUS, -speed/InvaderSaver.GAMESPEED_MODIFIER);
			}
		}
	}
	
	/**
	 * Clears any existing Invaders and generates a full new 55-strong wave of Invaders.
	 */
	public void startNewWave(){
		
		int x = BEGX;
		int y = (int)Math.min(BEGY + waveNum*(VSEP), MIN_WAVE_START_HEIGHT);
//		int y = BEGY;
//		y += waveNum*(VSEP); // start lower on consecutive waves.
//		y = (int)Math.min(y, MIN_WAVE_START_HEIGHT);
		waveNum++;
		
		// remove old Invaders if any remain.
		invaders.clear();
		
		// create a full wave of Invaders.
		for(int row = 1; row<6 ;y+=VSEP, row++)
		{
			for(int col = 1; col < 12; col++, x+=HSEP){
				Invader nextAbove = null;
				if(row > 1){
					// keep track of the next invader in line so we can activate the next Invader  
					// in the column to start firing if this one dies.
					nextAbove = invaders.get(invaders.size()-11);
				}
				makeInvader(x, y, row, row==5, nextAbove);  // create Invader; only the fifth row can fire at first.
			}
			x = BEGX;
		}
		
		direction = 1;
	}
	
	/**
	 * makes an Invader at (x, y), using row to determine which sprite it gets
	 * @param x
	 * @param y
	 * @param row
	 */
	private void makeInvader(int x, int y, int row, Boolean canFire, Invader nextInLine){
		// determine which sprite to use based on the row
		switch(row){
		case 1:
			invaders.add(new Invader(x, y, SpriteLib.INVADER_TOP, 30, canFire, nextInLine));
			break;
		case 2:
		case 3:
			invaders.add(new Invader(x, y, SpriteLib.INVADER_MID, 20, canFire, nextInLine));
			break;
		default: // row 4 or 5
			invaders.add(new Invader(x, y, SpriteLib.INVADER_LOW, 10, canFire, nextInLine));
			break;
		}
	}
	
	public void setWaveNum(int wave){
		Wave.waveNum = wave;
	}
	
	static public int getMovementInterval() {
		return moveCooldown;
	}
	
	static public int getTimeUntilNextMove() {
		return moveWait;
	}
	
	/**
	 * returns the movement distance of the Invaders, signed for direction
	 * @return
	 */
	static public int getInvaderMoveSpeed() {
		return InvaderMoveSpeed * direction;
	}
	
	public ArrayList<Invader> getInvaders(){
		return invaders;
	}
	
	public Bonus getBonus(){
		return bonus;
	}
	
	public void draw(Graphics g, ImageObserver io){
		for(Invader invader:invaders){
			invader.draw(g, io);
		}
		if(bonus!=null){
			bonus.draw(g, io);
		}
	}
}